Skip to content

Conversation

@adriangohjw
Copy link
Member

@adriangohjw adriangohjw commented Oct 17, 2025

Problem

Solution

Breaking Changes

  • Yes - this PR contains breaking changes
    • We switch from attempts to attemptsByIp column
  • No - this PR is backwards compatible with ALL of the following feature flags in this doc

Features:

  • Details ...

Improvements:

  • Details ...

Bug Fixes:

  • Details ...

Before & After Screenshots

BEFORE:

AFTER:

Tests

Deploy Notes

New environment variables:

  • env var : env var details
    • added env var to 1PW + SSM script (fetch_ssm_parameters.sh)

New scripts:

  • script : script details

New dependencies:

  • dependency : dependency details

New dev dependencies:

  • dependency : dependency details

Note

Adds per-IP OTP attempt limits backed by a new JSONB column and plumbs client IP through routes/services; also prevents issuing a new OTP while a valid one exists.

  • Database:
    • Add otps.attempts_by_ip (JSONB, default {}) via migration and expose as Otp.attemptsByIp.
  • Auth/OTP Logic:
    • Enforce per-IP OTP attempt limits in UsersService.verifyOtp using attemptsByIp; maintain legacy attempts only for monitoring.
    • Increment per-IP attempts atomically and reject when over auth.maxNumOtpAttempts; use "unknown" bucket when IP is missing.
    • Block sending a new OTP in sendEmailOtp if a non-expired OTP already exists.
    • Reset attempts and attemptsByIp on OTP issuance (sendEmailOtp, sendSmsOtp).
  • Routes/Service plumbing:
    • Capture client IP (cf-connecting-ip when secure, else req.ip) in v2/auth.verify and v2/authenticated/users OTP verify endpoints.
    • Thread clientIp through AuthService.verifyOtp, UsersService.verifyEmailOtp, and verifyMobileOtp.
  • Tests:
    • Add per-IP OTP verification tests covering incrementing, "unknown" IP bucket, and separate tracking per IP.

Written by Cursor Bugbot for commit b8c5c1b. This will update automatically on new commits. Configure here.

@adriangohjw adriangohjw self-assigned this Oct 17, 2025
@adriangohjw
Copy link
Member Author

bugbot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no bugs!


})
const email = rawEmail.toLowerCase()
const userInfo = (await this.authService.verifyOtp({ email, otp })).value
const userIp = isSecure ? req.get("cf-connecting-ip") : req.ip
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this line is making an assumption about Cloudflare's existence which we know may not necessarily be true. Noting this down as a nit for us to follow up as a separate fix as part of the Cloudflare post-mortem action items.

FYI will extract this out into a common function and add a comment there for follow-up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted in 9f90a78.

Comment on lines +236 to +238
// Return silently to avoid revealing whether an OTP exists
// This maintains security by not leaking information about existing OTPs
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I don't think we are able to directly return here because it is possible that the user is requesting for the OTP to be resent if the email did not reach their inbox. Instead we should be able to resend the email with the same OTP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants